﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Needle.Core.Reflection;
using Needle.Utils;

namespace Needle.Core.Factory
{
    /// <summary>
    /// Factory that uses a dynamic method to create instances
    /// </summary>
    public class DynamicMethodFactory : FactoryBase
    {
        /// <summary>
        /// initializes a dynamic method factory
        /// </summary>
        public DynamicMethodFactory(Type producedType)
            : base(producedType)
        { }
        /// <summary>
        /// initializes a dynamic method factory, and locks the constructor used
        /// </summary>
        public DynamicMethodFactory(Type producedType, ConstructorInfo ctor)
            : base(producedType)
        {
            Ensure.IsTrue(producedType == ctor.DeclaringType,
                "ctor", "ctor must be a constructor of type " + producedType.FullName);

            _ctor = new Constructor(ctor);
        }

        private Constructor _ctor = null;
        private List<Constructor> _ctors = new List<Constructor>();

        /// <summary>
        /// Creates a new instance with the paramters given.
        /// </summary>
        public override object CreateInstance(params object[] args)
        {
            Constructor ctor = _ctor;
            if (ctor != null)
                return ctor.Invoke(args);

            foreach (Constructor c in _ctors)
                if (c.Info.CanAccept(args))
                { ctor = c; break; }

            if (ctor != null)
                return ctor.Invoke(args);

            List<ConstructorInfo> infos = new List<ConstructorInfo>();
            foreach (ConstructorInfo ci in ProducedType.GetConstructors())
            {
                if (ci.CanAccept(args))
                    infos.Add(ci);
            }

            if (infos.Count == 0)
                throw new ArgumentException("No suitable constructor found.");

            if (infos.Count > 1)
            {
                StringBuilder sb = new StringBuilder();
                foreach (ConstructorInfo info in infos)
                {
                    sb.Append((new MethodSignature(info)).ToString());
                    sb.Append(", ");
                }
                sb.Length -= 2;
                throw new ArgumentException("Ambiguous constructors found: " + sb.ToString());
            }

            ctor = new Constructor(infos[0]);
            _ctors.Add(ctor);
            return ctor.Invoke(args);
        }

        private Type[] GetSignature(params object[] args)
        {
            List<Type> signature = new List<Type>(args.Length);
            foreach (object arg in args)
            {
                if (arg == null)
                    signature.Add(null);
                else
                    signature.Add(arg.GetType());
            }
            return signature.ToArray();
        }

    }
}
